package client;

import java.awt.Cursor;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.*;
import java.util.*;

import util.ActionSet;
import util.GameConstants;
import util.UIConstants;

public class GameController {
	public GameModel model;
	public ClientProtocol protocol;
	public int mplayerID;
	public GameView gameWindow;
	

	boolean wDown = false;
	boolean aDown = false;
	boolean sDown = false;
	boolean dDown = false;
	boolean wPressedThisTurn = false;
	boolean aPressedThisTurn = false;
	boolean sPressedThisTurn = false;
	boolean dPressedThisTurn = false;
	int mcastID = -1;
	double mcastTargetX = 0;
	double mcastTargetY = 0;
	int mturnID = -1;
	Thread repaintThread;
	int castingSpell = -1;
	
	public GameController(GameView tview, ClientModel tmodel, ClientProtocol tprotocol) {
		this.model = tmodel.game;
		this.mplayerID = tmodel.playerID;
		this.protocol = tprotocol;
		this.gameWindow = tview;
		wDown = false;
		aDown = false;
		sDown = false;
		dDown = false;
		wPressedThisTurn = false;
		aPressedThisTurn = false;
		sPressedThisTurn = false;
		dPressedThisTurn = false;
		mcastID = -1;
		castingSpell = -1;
		
		gameWindow.addKeyListener(new KeyListener() {

			@Override
			public void keyPressed(KeyEvent e) {
				if(e.getKeyCode()==KeyEvent.VK_W) {
					if(wDown) return;
					wDown = true;
					wPressedThisTurn = true;
				} else if(e.getKeyCode()==KeyEvent.VK_A) {
					if(aDown) return;
					aDown = true;
					aPressedThisTurn = true;
				} else if(e.getKeyCode()==KeyEvent.VK_S) {
					if(sDown) return;
					sDown = true;
					sPressedThisTurn = true;
				} else if(e.getKeyCode()==KeyEvent.VK_D) {
					if(dDown) return;
					dDown = true;
					dPressedThisTurn = true;
				} else if(e.getKeyCode()==KeyEvent.VK_ESCAPE) {
					setCastingMode(-1);
				} else if(e.getKeyCode()==KeyEvent.VK_R) {
					registerRCast();
				} else if(e.getKeyCode()==KeyEvent.VK_T) {
					registerTCast();
				} else if(e.getKeyCode()==KeyEvent.VK_Y) {
					registerYCast();
				} else if(e.getKeyCode()==KeyEvent.VK_F) {
					registerFCast();
				} else if(e.getKeyCode()==KeyEvent.VK_G) {
					registerGCast();
				} else if(e.getKeyCode()==KeyEvent.VK_H) {
					registerHCast();
				} else if(e.getKeyCode()==KeyEvent.VK_SPACE) {
					registerSpaceCast();				
				}
			}
			@Override
			public void keyReleased(KeyEvent e) {
				if(e.getKeyCode()==KeyEvent.VK_W) {
					wDown = false;
				}
				else if(e.getKeyCode()==KeyEvent.VK_A) {
					aDown = false;
				}
				else if(e.getKeyCode()==KeyEvent.VK_S) {
					sDown = false;
				}
				else if(e.getKeyCode()==KeyEvent.VK_D) {
					dDown = false;
				}
			}
			@Override
			public void keyTyped(KeyEvent arg0) {
			}
		});
		
		gameWindow.addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseDragged(MouseEvent e) {
			}
			@Override
			public void mouseMoved(MouseEvent e) {
			}
		});
		
		gameWindow.addMouseListener(new MouseListener() {
			@Override
			public void mouseClicked(MouseEvent e) {
			}
			@Override
			public void mouseEntered(MouseEvent e) {
			}
			@Override
			public void mouseExited(MouseEvent e) {
			}
			@Override
			public void mousePressed(MouseEvent e) {
				int x = e.getX(); 
				int y = e.getY();
				if(x>=0 && x<UIConstants.screenSize && y>=0 && y<UIConstants.screenSize)
					registerMousePress(x, y);
			}
			@Override
			public void mouseReleased(MouseEvent e) {
			}
		});
		
		Thread turnLoopThread = new Thread(new Runnable() {
			public void run() {
				long threadStart = System.currentTimeMillis();
				long startTime = threadStart;
				long endTime = startTime;
				while(true) {
					startTime = System.currentTimeMillis();
					endTurn();
					try {
						endTime = System.currentTimeMillis();
						long sleepTime = GameConstants.turnMillis-(endTime-startTime);
						//System.out.println("p"+mplayerID+", ending turn"+mturnID+", at"+(startTime-threadStart)+", sleeping for "+sleepTime+"ms");
						if(sleepTime>0)
							Thread.sleep(sleepTime);
					} catch (InterruptedException e) {
						System.out.println("turn loop interrupted!");
						return;
					}
				}
			}
		});
		repaintThread = new Thread( new Runnable() {
			public void run() {
				while(true) {
					gameWindow.repaint();
					try {
						long sleepTime = GameConstants.turnMillis/UIConstants.framesPerTurn;
						Thread.sleep(sleepTime);
					} catch (InterruptedException e) {
						System.out.println("repaint loop interrupted!");
						return;
					}
				}
			}
		});
		mturnID = 1;
		turnLoopThread.start();
		repaintThread.start();
	}
	
	public int getCastingMode() {
		return castingSpell;
	}
	public void setCastingMode(int spellID) {
		castingSpell = spellID;
		if(castingSpell==-1)
			gameWindow.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		else 
			gameWindow.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));	
	}
	/** The button or key has been pressed to start casting the given spell ID, now set the casting mode appropriately. */
	public boolean startCast(int spellID) {
		if(spellID==-1) return false;
		if(mcastID!=-1 || !model.canCast(mplayerID, spellID))
			return false;
		
		if(spellID!=5) // everything except haste needs a cast mode
			setCastingMode(spellID);
		else {
			mcastID = spellID; 
			setCastingMode(-1);
		}
		return true;
	}
	public boolean registerRCast() {
		return startCast(1);
	}
	public boolean registerTCast() {
		return startCast(2);
	}
	public boolean registerYCast() {
		return startCast(3);
	}
	public boolean registerFCast() {
		return startCast(4);
	}
	public boolean registerGCast() {
		return startCast(5);
	}
	public boolean registerHCast() {
		return startCast(6);
	}
	public boolean registerSpaceCast() {
		if(mcastID!=-1 || !model.canCast(mplayerID, 0))
			return false; 
		setCastingMode(0);
		return true;
	}
	public void registerMousePress(int xMouse, int yMouse) {
		if(getCastingMode()!=-1) {
			if(!model.canCast(mplayerID, castingSpell))
				return;
			mcastID = castingSpell; 
			mcastTargetX = gameWindow.xpixelToCoord(xMouse);
			mcastTargetY = gameWindow.ypixelToCoord(yMouse);
			setCastingMode(-1);
		}
	}

	
	public void endTurn() {
		// compute action set
		ActionSet as = new ActionSet();
		as.playerID = mplayerID;
		as.turnID = mturnID;
		if(wPressedThisTurn) 
			as.moveY += -1;
		if(aPressedThisTurn) 
			as.moveX += -1;
		if(sPressedThisTurn) 
			as.moveY += 1;
		if(dPressedThisTurn) 
			as.moveX += 1;
		as.castID = mcastID;
		if(mcastID!=-1) {
			as.castTargetX = mcastTargetX;
			as.castTargetY = mcastTargetY;
		}
		
		// reset listener variables
		wPressedThisTurn = wDown;
		aPressedThisTurn = aDown;
		sPressedThisTurn = sDown;
		dPressedThisTurn = dDown;
		mcastID = -1;
		
		// send out action set
		protocol.sendActionSet(as);
		
		// wait for others' actions, read everyone's action set
		TreeMap<Integer,ActionSet> actions = new TreeMap<Integer,ActionSet>();
		actions.put(mplayerID, as);
		ActionSet tas = protocol.readActionSet();
		actions.put(tas.playerID, tas);
		
		// for each player, process actions
		gameWindow.setRepaintFlag( false ); // stop repainting before changing the model to avoid concurrency errors
		while(gameWindow.isPainting()) { // check that view is not already in the middle of painting
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}	
		for(Integer i: actions.keySet()) {
			processActionSet(actions.get(i));
		}	
		
		// contact model and view and tell them to go to the next turn
		model.turnLoop();
		mturnID++;
		/** timestamp the end of the turn and the start of the next turn */
		long turnStartTime = System.currentTimeMillis();
		gameWindow.endTurn(turnStartTime);
		gameWindow.setRepaintFlag( true );
		
	}
	
	public void processActionSet(ActionSet as) {
		//if(as.castID!=-1) System.out.println("Player "+mplayerID +" processing action set: "+as.toString());
		model.setPlayerVelocity(as.playerID, as.moveX*GameConstants.moveSpeed, as.moveY*GameConstants.moveSpeed);
		if(as.castID!=-1)
			model.castSpell(as.playerID, as.castID, as.castTargetX, as.castTargetY);
		
	}
	
	
	
}
